/**
\file   ucmidi.c
\brief  uC-MIDI Library (MIDI for Microcontrollers).

Copyright (c) 2013 Gene Kozin <gkozin(at)gmail.com>

This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the Free
Software Foundation, either version 3 of the License, or (at your option)
any later version.

This program is distributed in the hope that it will be useful, but WITHOUT
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
more details.

You should have received a copy of the GNU General Public License along
with this program.  If not, see <http://www.gnu.org/licenses/>.
**/

#include <ucmidi.h>

/* Null pointer */
#ifndef NULL
# define NULL ((void*)0)
#endif

/* Receive Buffer Size (2 bytes minimum) */
#if UCMIDI_MAX_SYSEX_SIZE > 2
# define RXBUF_SIZE     UCMIDI_MAX_SYSEX_SIZE
#else
# define RXBUF_SIZE     2
#endif

/* Transmit Buffer Size (3 bytes minimum) */
#if UCMIDI_MAX_SYSEX_SIZE > 3
# define TXBUF_SIZE     UCMIDI_MAX_SYSEX_SIZE
#else
# define TXBUF_SIZE     3
#endif

/**
\brief  Returs uC-MIDI library version.
**/
int ucmidi_version(void)
{
    return MIDI_MAKE_INT(UCMIDI_VERSION_MINOR, UCMIDI_VERSION_MAJOR);
}

#ifdef UCMIDI_HAS_MIDI_PARSER
/***************************************************************************
MIDI Parser Implementation
***************************************************************************/

/* MIDI Parser Context */
typedef struct {
    midi_callback_t const* cb;  /* pointer to MIDI callback structure */
    int len;                    /* message length */
    BYTE cnt;                   /* byte count */
    BYTE cmd;                   /* command byte */
    BYTE rxbuf[RXBUF_SIZE];     /* data buffer */
} ucmidi_ctx_t;

/* Forward references */
static void midi_dispatch(BYTE cmd, BYTE const* data, int len);
static int get_data_length(BYTE cmd);

/* Static parser context */
static ucmidi_ctx_t _ctx;

/**
\brief  Initializes uC-MIDI parser.
**/
void ucmidi_init(midi_callback_t const* cb)
{
    BYTE* p = (void*)&_ctx;
    int i = sizeof _ctx;

    /* clear parser context */
    while (i--) {
       *p++ = 0;
    }

    /* save callback pointer */
    _ctx.cb = cb;
}

/**
\brief  Parses MIDI input data.
**/
void midi_parser(BYTE c)
{
    if (c & MIDI_CMD_FLAG) {
        /* handle new command */
        if (MIDI_EOX == c) {
            /* SysEx message requires special handling */
            if (MIDI_SYSEX != _ctx.cmd || 0 == _ctx.cnt) {
                /* incomplete SysEx message; reset parser */
                goto _reset_;
            }
        } else {
            /* get the number of data bytes that follows */
            _ctx.len = get_data_length(c);
            if (_ctx.len < 0) {
                /* invalid command; reset parser */
                goto _reset_;
            }

            _ctx.cmd = c;   /* save command code */
            _ctx.cnt = 0;   /* reset data byte count */
            if (_ctx.len > 0) {
                /* exit parser and wait for more data */
                return;
            }
        }
    } else {
        /* handle data byte */
        if (0 == _ctx.cmd || _ctx.cnt >= sizeof _ctx.rxbuf) {
            /* data out of synch or buffer full; reset parser */
            goto _reset_;
        }

        /* store received data byte */
        _ctx.rxbuf[_ctx.cnt++] = c;
        if (MIDI_SYSEX == _ctx.cmd || --_ctx.len > 0) {
            /* wait for more data */
            return;
        }
    }

    /* if we got here, then we have received compete MIDI message */
    midi_dispatch(_ctx.cmd, _ctx.rxbuf, _ctx.cnt);

_reset_:
    /* reset parser */
    _ctx.cmd = 0;
    _ctx.cnt = 0;
}

/**
\brief  Dispatches MIDI message to the appropriate message handler.
**/
void midi_dispatch(BYTE cmd, BYTE const* data, int len)
{
    BYTE chan = 0;

    if (NULL == _ctx.cb) {
        /* callbacks not set; ignore */
        return;
    }

    if (cmd < MIDI_SYSEX) {
        chan = MIDI_GET_CHAN(cmd);
        cmd = MIDI_GET_CMD(cmd);
    }

    switch ((unsigned)cmd) {
    case MIDI_NOTE_OFF:
        if (NULL != _ctx.cb->note_off) {
            _ctx.cb->note_off(chan, data[0], data[1]);
        }
        break;

    case MIDI_NOTE_ON:
        if (NULL != _ctx.cb->note_on) {
            _ctx.cb->note_on(chan, data[0], data[1]);
        }
        break;

    case MIDI_KEY_PRESSURE:
        if (NULL != _ctx.cb->key_pressure) {
            _ctx.cb->key_pressure(chan, data[0], data[1]);
        }
        break;

    case MIDI_CC:
        if (NULL != _ctx.cb->control_change) {
            _ctx.cb->control_change(chan, data[0], data[1]);
        }
        break;

    case MIDI_PITCH_BEND:
        if (NULL != _ctx.cb->pitch_bend) {
            _ctx.cb->pitch_bend(chan, MIDI_MAKE_INT(data[0], data[1]));
        }
        break;

    case MIDI_PROGRAM:
        if (NULL != _ctx.cb->program_change) {
            _ctx.cb->program_change(chan, data[0]);
        }
        break;

    case MIDI_CHAN_PRESSURE:
        if (NULL != _ctx.cb->chan_pressure) {
            _ctx.cb->chan_pressure(chan, data[0]);
        }
        break;

    case MIDI_SYSEX:
        if (NULL != _ctx.cb->sysex) {
            _ctx.cb->sysex(data, len);
        }
        break;

    case MIDI_TIME_CODE:
        if (NULL != _ctx.cb->time_code) {
            _ctx.cb->time_code(data[0]);
        }
        break;

    case MIDI_SONG_POSITION:
        if (NULL != _ctx.cb->song_position) {
            _ctx.cb->song_position(MIDI_MAKE_INT(data[0], data[1]));
        }
        break;

    case MIDI_SONG_SELECT:
        if (NULL != _ctx.cb->song_select) {
            _ctx.cb->song_select(data[0]);
        }
        break;

    case MIDI_TUNE_REQUEST:
        if (NULL != _ctx.cb->tune_request) {
            _ctx.cb->tune_request();
        }
        break;

    case MIDI_CLOCK:
        if (NULL != _ctx.cb->clock) {
            _ctx.cb->clock();
        }
        break;

    case MIDI_START:
        if (NULL != _ctx.cb->start) {
            _ctx.cb->start();
        }
        break;

    case MIDI_CONTINUE:
        if (NULL != _ctx.cb->cont) {
            _ctx.cb->cont();
        }
        break;

    case MIDI_STOP:
        if (NULL != _ctx.cb->stop) {
            _ctx.cb->stop();
        }
        break;

    case MIDI_ACTIVE_SENSE:
        if (NULL != _ctx.cb->active_sense) {
            _ctx.cb->active_sense();
        }
        break;

    case MIDI_SYSTEM_RESET:
        if (NULL != _ctx.cb->reset) {
            _ctx.cb->reset();
        }
        break;
    }
}

/**
\brief  Returns the number of data bytes to read for a given command or -1
        if the command is invalid.
**/
int get_data_length(BYTE cmd)
{
    if (cmd < MIDI_SYSEX) {
        /* ignore MIDI channel */
        cmd = MIDI_GET_CMD(cmd);
    }

    switch (cmd) {
    case MIDI_NOTE_OFF:         /* Note Off */
    case MIDI_NOTE_ON:          /* Note On */
    case MIDI_KEY_PRESSURE:     /* Polyphonic Key Pressure */
    case MIDI_CC:               /* Controller Change */
    case MIDI_PITCH_BEND:       /* Pitch Bend */
    case MIDI_SONG_POSITION:    /* Song Position Pointer */
        return 2;

    case MIDI_PROGRAM:          /* Program Change */
    case MIDI_CHAN_PRESSURE:    /* Channel Pressure */
    case MIDI_SYSEX:            /* start of a MIDI SysEx message */
    case MIDI_TIME_CODE:        /* Time Code */
    case MIDI_SONG_SELECT:      /* Song Select */
        return 1;

    case MIDI_TUNE_REQUEST:     /* Tune Request */
    case MIDI_CLOCK:            /* Timing Clock */
    case MIDI_START:            /* Start */
    case MIDI_CONTINUE:         /* Continue */
    case MIDI_STOP:             /* Stop */
    case MIDI_ACTIVE_SENSE:     /* Active Sense */
    case MIDI_SYSTEM_RESET:     /* System Reset */
        return 0;

    default:                    /* invalid command */
        return -1;
    }
}

#endif /* UCMIDI_HAS_MIDI_PARSER */

#ifdef UCMIDI_HAS_MIDI_SEND
/***************************************************************************
MIDI Send APIs
***************************************************************************/

/* Transmit buffer */
static BYTE _sendbuf[TXBUF_SIZE];
static int _error;

/**
\brief  Returns result of the last MIDI send operation.
**/
int midi_errno(void)
{
    return _error;
}

/**
\brief  Sends MIDI xxx message.
**/
void send_note_on(BYTE chan, BYTE note, BYTE velocity)
{
    _sendbuf[0] = MIDI_NOTE_ON | (chan & MIDI_CHAN_MASK);
    _sendbuf[1] = note & MIDI_DATA_MASK;
    _sendbuf[2] = velocity & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 3);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_note_off(BYTE chan, BYTE note, BYTE velocity)
{
    _sendbuf[0] = MIDI_NOTE_OFF | (chan & MIDI_CHAN_MASK);
    _sendbuf[1] = note & MIDI_DATA_MASK;
    _sendbuf[2] = velocity & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 3);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_key_pressure(BYTE chan, BYTE note, BYTE pressure)
{
    _sendbuf[0] = MIDI_KEY_PRESSURE | (chan & MIDI_CHAN_MASK);
    _sendbuf[1] = note & MIDI_DATA_MASK;
    _sendbuf[2] = pressure & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 3);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_control_change(BYTE chan, BYTE number, BYTE value)
{
    _sendbuf[0] = MIDI_CC | (chan & MIDI_CHAN_MASK);
    _sendbuf[1] = number & MIDI_DATA_MASK;
    _sendbuf[2] = value & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 3);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_program_change(BYTE chan, BYTE number)
{
    _sendbuf[0] = MIDI_PROGRAM | (chan & MIDI_CHAN_MASK);
    _sendbuf[1] = number & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 2);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_chan_pressure(BYTE chan, BYTE pressure)
{
    _sendbuf[0] = MIDI_CHAN_PRESSURE | (chan & MIDI_CHAN_MASK);
    _sendbuf[1] = pressure & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 2);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_pitch_bend(BYTE chan, int value)
{
    _sendbuf[0] = MIDI_PITCH_BEND | (chan & MIDI_CHAN_MASK);
    _sendbuf[1] = MIDI_GET_LSB(value);
    _sendbuf[2] = MIDI_GET_MSB(value);
    _error = midi_send(_sendbuf, 3);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_sysex(BYTE const* data, int len)
{
    BYTE* dest;
    int cnt;

    if (len < 1 || len > UCMIDI_MAX_SYSEX_SIZE) {
        /* error: invalid message length */
		_error = -1;
        return;
    }

    _sendbuf[0] = MIDI_SYSEX;
    for (dest = &_sendbuf[1], cnt = len; cnt--;) {
        *dest++ = *data++;
    }
    *dest = MIDI_EOX;
    _error = midi_send(_sendbuf, len + 2);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_time_code(BYTE value)
{
    _sendbuf[0] = MIDI_TIME_CODE;
    _sendbuf[1] = value & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 2);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_song_position(int position)
{
    _sendbuf[0] = MIDI_SONG_POSITION;
    _sendbuf[1] = MIDI_GET_LSB(position);
    _sendbuf[2] = MIDI_GET_MSB(position);
    _error = midi_send(_sendbuf, 3);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_song_select(BYTE number)
{
    _sendbuf[0] = MIDI_SONG_SELECT;
    _sendbuf[1] = number & MIDI_DATA_MASK;
    _error = midi_send(_sendbuf, 2);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_tune_request(void)
{
    _sendbuf[0] = MIDI_TUNE_REQUEST;
    _error = midi_send(_sendbuf, 1);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_clock(void)
{
    _sendbuf[0] = MIDI_CLOCK;
    _error = midi_send(_sendbuf, 1);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_start(void)
{
    _sendbuf[0] = MIDI_START;
    _error = midi_send(_sendbuf, 1);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_continue(void)
{
    _sendbuf[0] = MIDI_CONTINUE;
    _error = midi_send(_sendbuf, 1);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_stop(void)
{
    _sendbuf[0] = MIDI_STOP;
    _error = midi_send(_sendbuf, 1);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_active_sense(void)
{
    _sendbuf[0] = MIDI_ACTIVE_SENSE;
    _error = midi_send(_sendbuf, 1);
}

/**
\brief  Sends MIDI xxx message.
**/
void send_reset(void)
{
    _sendbuf[0] = MIDI_SYSTEM_RESET;
    _error = midi_send(_sendbuf, 1);
}

#endif /* UCMIDI_HAS_MIDI_SEND */
